const _ = require('lodash');
import assert from 'assert';
import SB from '../string-buffer';
const debug = require('debug')('db:sql');

/**
 * SQL语句生成工具（尽量保证兼容SQL92标准）
 *
 * 子类必须实现下列方法：id,limit
 */
export default class SQL {
    constructor() {
        this.sql = new SB();
    }

    clear() {
        this.sql.clear();
        this.option = undefined;
    }

    /////////////////////////////////////////////////////////////////
    // DDL

    createDb(opt) {
        if (typeof opt === 'string') {
            opt = {db: opt};
        }
        const sql = new SB('CREATE DATABASE ');
        if (opt.exists) {
            sql.add('IF NOT EXISTS ');
        }
        sql.add(this.id(opt.db));
        return sql;
    }

    removeDb(db, ...opt) {
        const sql = new SB('DROP DATABASE ');
        if (opt.length && opt[0]) {
            sql.add('IF EXISTS ');
        }
        sql.add(db);
        return sql;
    }

    createTable(opt) {
        // TODO 完善字段定义预处理
        opt.fields.forEach(field => {
            if (field.key || field.unique) {
                // 主键和唯一值不能为空
                field.not_null = true;
            }
        });
        const sql = new SB('CREATE TABLE ');
        if (opt.exists) {
            sql.add('IF NOT EXISTS ');
        }
        sql.add(this.id(opt.db, opt.table));
        const fields = this.defineFields(opt);
        sql.add(' (').add(fields.join(',')).add(')');
        return sql;
    }

    /**
     * 创建生成表用的字段定义
     * @param opt 参数：{
     * name - 名称
     * type - 类型
     * length - 长度
     * decimal - 精度
     * default - 默认值
     * not_null - 不空
     * auto - 是否自增长
     * unique - 是否唯一
     * key - 是否主键
     * }
     * @return {Array}
     */
    defineFields(opt) {
        return opt.fields.map(f => {
            const field = new SB();
            // 名称
            field.add(this.id(f.name));
            // 类型
            const type = this.type(f.type, f.length, f.decimals);
            field.add(' ').add(type);
            // 非空
            field.add(f.not_null ? ' NOT NULL' : ' NULL');
            // 默认值
            if (f.default) {
                let value = f.default;
                if (typeof value === 'string') {
                    value = `'${value}'`;
                }
                field.add(' DEFAULT ').add(value);
            }
            // 自增长
            if (f.auto) {
                field.add(' AUTO_INCREMENT');
            }
            // 唯一键
            if (f.unique) {
                field.add(' UNIQUE');
            }
            // 主键
            if (f.key) {
                field.add(' KEY');
            }
            return field;
        });
    }

    updateTable(opt) {
        const sql = new SB('ALTER TABLE ');
        sql.add(this.id(opt.db, opt.table));
        // TODO 修改语句生成
        return sql;
    }

    removeTable(opt) {
        const sql = new SB('DROP TABLE ');
        if (opt.exists) {
            sql.add('IF EXISTS ');
        }
        sql.add(this.id(opt.db, opt.table));
        return sql;
    }

    /////////////////////////////////////////////////////////////////
    // DML

    query(opt) {
        if (typeof opt === 'string') {
            // 直接传入SQL语句
            return new SB(opt);
        }
        this.clear();
        this.option = opt;
        this.makeSelect();
        this.makeWhere();
        this.makeOrder();
        this.makeLimit();
        return this;
    }

    count(opt) {
        this.clear();
        this.option = opt;
        opt.fields = 'COUNT(1)';
        this.makeSelect();
        this.makeWhere();
        return this;
    }

    insert(opt) {
        this.clear();
        this.option = opt;
        return this;
    }

    update(opt) {
        this.clear();
        this.option = opt;
        return this;
    }

    delete(opt) {
        this.clear();
        this.option = opt;
        return this;
    }

    makeSelect() {
        this.sql.add('SELECT ')
            .add(this.makeFields());
        const {db, table} = this.option;
        if (table) {
            this.sql.add(' FROM ')
                .add(this.id(db, table));
        }
    }

    makeFields() {
        const {fields} = this.option;
        if (Array.isArray(fields)) {
            return fields.map(f => {
                if (typeof f === 'object') {
                    const sb = new SB();
                    const field = this.id(f.db, f.field);
                    if (field) {
                        sb.add(field);
                    } else if (f.expr) {
                        sb.add(f.expr);
                    }
                    if (f.name) {
                        sb.add(' AS ').add(this.id(f.name));
                    }
                    return sb.str();
                }
                if (typeof f === 'string') {
                    return this.id(f);
                }
                return '';
            }).join(', ');
        } else {
            return fields || '*';
        }
    }

    makeLimit() {
        let {start, count, page} = this.option;
        // 直接指定起始位置和数量
        start = parseInt(start);
        count = parseInt(count);
        // 通过页号和每页数量指定
        if (!Number.isInteger(start) && count > 0) {
            page = parseInt(page);
            if (page > 0) {
                start = (page - 1) * count;
            }
        }
        const limit = this.limit(start, count);
        if (limit) {
            this.sql.add(limit);
        }
    }

    makeWhere() {
        const {where} = this.option;
        if (!where) return;
        this._params = [];
        this.sql.add(' WHERE ');
        if (typeof where === 'object') {
            this.sql.add(this.where(where));
        } else if (typeof where === 'string') {
            this.sql.add(where);
        }
    }

    where(opt) {
        if (Array.isArray(opt)) {
            return '(' + this.or(opt) + ')';
        }
        const result = _.map(opt, (value, key) => {
            if (Array.isArray(value)) {
                return '(' + this.or(value) + ')';
            }
            key = this.id(key);
            if (typeof value === 'object') {
                const keys = _.keys(value);
                assert(keys.length === 1, '一个字段只能有一个操作符');
                const op = keys[0];
                return this.op(key, op, value[op]);
            } else {
                return this.op(key, '$eq', value);
            }
        });
        return result.join(' AND ');
    }

    or(opt) {
        const result = _.map(opt, item => this.where(item));
        if (!_.isEmpty(result)) {
            return result.join(' OR ');
        }
    }

    op(key, action, value) {
        const sb = new SB(key);
        let param = true;
        switch (action) {
        case '$eq':
            sb.add('=?');
            break;
        case '$ne':
            sb.add('!=?');
            break;
        case '$lt':
            sb.add('<?');
            break;
        case '$lte':
            sb.add('<=?');
            break;
        case '$gt':
            sb.add('>?');
            break;
        case '$gte':
            sb.add('>=?');
            break;
        case '$in':
            sb.add(' IN (').add(_.map(value, () => '?').toString()).add(')');
            break;
        case '$bt':
            assert(value.length === 2, 'BETWEEN 操作符必须且只能有两个参数');
            sb.add(' BETWEEN ').add(_.map(value, () => '?').join(' AND '));
            break;
        case '$null':
            sb.add(' IS NULL');
            param = false;
            break;
        case '$nn':
            sb.add(' IS NOT NULL');
            param = false;
            break;
        default:
            throw new Error(`Unknown operator: ${action}`);
        }
        if (param) {
            this._params = _.concat(this._params, value);
        }
        return sb.str();
    }

    get params() {
        return this._params;
    }

    makeOrder() {
        const {order} = this.option;
        if (order) {
            this.sql.add(' ORDER BY ')
                .add(this.order(order));
        }
    }

    order(opt) {
        if (Array.isArray(opt)) {
            return opt.map(this.order.bind(this)).join(', ');
        } else if (typeof opt === 'object') {
            const field = opt.field || opt.name;
            let expr = this.id(field);
            if (opt.desc) expr += ' DESC';
            return expr;
        } else if (typeof opt === 'string') {
            return this.id(opt);
        }
    }

    str() {
        const sql = this.sql.str();
        debug(sql);
        return sql;
    }

    toString() {
        return this.str();
    }
}
